package com.cms.common.lang;

import java.io.IOException;
import java.io.InputStream;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;

/** 
 * c3p0连接池操作.
 * <ul>
 * 	<li>依赖的jar包有：c3p0-0.9.1.jar</li>
 * </ul>
 */
public class DataBaseHandle {

	/** c3p0连接池 */
	private ComboPooledDataSource cpds;
	
	/** 构造. */
	public DataBaseHandle(String driver, String url, String user, String password, Integer maxPoolSize) {
		try {
			//创建连接池
			cpds = new ComboPooledDataSource();
			
			//用户设置
			cpds.setDriverClass(driver);						//驱动
			cpds.setJdbcUrl(url);								//连接字符串
			cpds.setUser(user);									//用户名
			cpds.setPassword(password);							//密码
			cpds.setMaxPoolSize(maxPoolSize);					//连接池中保留的最大连接数（默认：15）
			
			//默认设置
			cpds.setMinPoolSize(1);								//连接池中保留的最小连接数（默认：0）
			cpds.setInitialPoolSize(1);							//初始化时获取几个连接，取值应在minPoolSize与maxPoolSize之间（默认：3）
			cpds.setAcquireIncrement(1);						//当连接池中的连接耗尽的时候c3p0一次同时获取的连接数（默认：3）
			cpds.setCheckoutTimeout(1000);						//当连接池用完时客户端调用getConnection()后等待获取新连接的时间，超时后将抛出SQLException，如设为0则无限期等待，单位毫秒（默认：0）
			cpds.setMaxIdleTime(25000);							//最大空闲时间，定义多少秒内未使用则连接被丢弃，若为0则永不丢弃（默认：0）
			cpds.setIdleConnectionTestPeriod(18000);			//隔多少秒检查所有连接池中的空闲连接，0表示不检查（默认：0）
			cpds.setDebugUnreturnedConnectionStackTraces(true);	//启用之后(true)，对于每个从连接池拿出去的数据库连接，如果一段时间(unreturnedConnectionTimeout)内没有归还，C3P0就会强制关闭这个连接，并将获取连接时的stack trace，以抛出异常的方式显示出来（默认：false）
			cpds.setUnreturnedConnectionTimeout(600);			//用于设置开启debugUnreturnedConnectionStackTraces后的超时时间（单位：秒）
			cpds.setTestConnectionOnCheckin(true);				//如果设为true那么在取得连接的同时将校验连接的有效性（默认：false）
			cpds.setMaxStatements(100);							//JDBC的标准参数，用以控制数据源内加载的PreparedStatements数量，但由于预缓存的statements属于单个connection而不是整个连接池，所以设置这个参数需要考虑到多方面的因素。如果maxStatements与maxStatementsPerConnection均为0，则缓存被关闭（默认：0)
			cpds.setAutomaticTestTable("T_TEST_C3P0");			//c3p0将建一张名为T_TEST_C3P0的空表，并使用其自带的查询语句进行测试。如果定义了这个参数那么属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作，它将只供c3p0测试使用（默认: null）
			
			//取连接(测试)
			cpds.getConnection().close();
		} catch (Throwable e) {
			throw new RuntimeException("连接池创建失败", e);
		}
	}
	
	
	
	//**************************c3p0方法****************************
	
	/** sql语句执行（增删改），返回指定类型查询结果（默认影响记录条数，可选pk-主键）. */
	public Object execute(String sql, String resultType) {
		Connection con			= null;
		PreparedStatement stat	= null;
		ResultSet rs			= null;
		try {
			con					= cpds.getConnection();
			if ("pk".equals(resultType)) {
				stat			= con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
				stat.execute();
				rs				= stat.getGeneratedKeys();
				return rs.next() ? rs.getObject(1) : null;
			} else {
				stat			= con.prepareStatement(sql);
				return stat.executeUpdate();
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			close(stat, con, rs);
		}
	}
	
	/** sql语句查询，返回指定类型查询结果（默认List<Map<String,Object>>）. */
	public Object query(String sql, String resultType) {
		Connection con			= null;
		PreparedStatement stat	= null;
		ResultSet rs			= null;
		try {
			con					= cpds.getConnection();
			stat				= con.prepareStatement(sql);
			rs					= stat.executeQuery();	//执行语句
			return getResult(rs, resultType);			//封装结果
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			close(stat, con, rs);
		}
	}
	
	/** 存储过程执行（增删改），返回影响记录条数. */
	public int spExecute(String sql) {
		Connection con			= null;
		CallableStatement stat	= null;
		try {
			con					= cpds.getConnection();
			stat				= con.prepareCall(sql);
			int num 			= stat.executeUpdate();
			return num;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			close(stat, con, null);
		}
	}
	
	/** 存储过程查询，返回指定类型查询结果（默认List<Map<String,Object>>）. */
	public Object spQuery(String sql, String resultType) {
		Connection con			= null;
		CallableStatement stat	= null;
		ResultSet rs			= null;
		try {
			con					= cpds.getConnection();
			stat				= con.prepareCall(sql);
			rs					= stat.executeQuery();
			return getResult(rs, resultType);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			close(stat, con, rs);
		}
	}
	
	/** 文件保存，返回主键，sql中文件字段用?表示. */
	public Object fileExecute(String sql, InputStream inputStream) {
		Connection con			= null;
		PreparedStatement stat	= null;
		ResultSet rs			= null;
		try {
			con					= cpds.getConnection();
			stat				= con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
			stat.setBinaryStream(1, inputStream, inputStream.available());
			stat.execute();
			rs					= stat.getGeneratedKeys();
			return rs.next() ? rs.getObject(1) : null;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			try {
				inputStream.close();	//TODO 是否能自动关闭？
			} catch (IOException e) {
				throw new RuntimeException(e);
			}	
			close(stat, con, rs);
		}
	}
	
	/** sql语句批量执行（增删改），返回影响记录总条数. */
	public int execute(List<String> sqls) {
		Connection con	= null;
		Statement stat	= null;
		try {
			con			= cpds.getConnection();
			stat		= con.createStatement();
			for (String sql : sqls) {
				stat.addBatch(sql);
			}
			int[] is	= stat.executeBatch();
			int count	= 0;
			for (int i : is) {
				count += i;
			}
			return count;
		} catch (SQLException e) {
			throw new RuntimeException(e);
		} finally {
			close(stat, con, null);
		}
	}
	
	/** 连接池销毁. */
	public void destory() {
		try {
			DataSources.destroy(cpds);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}
	
	
	
	//**************************私有方法****************************
	
	/** 封装结果. */
	private Object getResult(ResultSet resultSet, String resultType) throws SQLException {
		if ("simple".equals(resultType)) {		//单列单行
			return resultSet.next() ? resultSet.getObject(1) : null;
		} else if ("simples".equals(resultType)) {	//单列多行
			List<Object> rows			= new ArrayList<Object>();
			while (resultSet.next()) {
				rows.add(resultSet.getObject(1));
			}
			return rows;
		} else if ("object".equals(resultType)) {	//多列单行
			ResultSetMetaData metaData		= resultSet.getMetaData();
			Map<String,Object> row			= null;
			if (resultSet.next()) {
				row							= new HashMap<String,Object>();
				for (int i = 1; i <= metaData.getColumnCount(); i++) {
					String name				= metaData.getColumnLabel(i);
					row.put(name, resultSet.getObject(name));
				}
			}
			return row;
		} else {								//多列多行
			ResultSetMetaData metaData		= resultSet.getMetaData();
			List<Map<String,Object>> rows	= new ArrayList<Map<String,Object>>();		//封装结果
			while (resultSet.next()) {
				Map<String,Object> row		= new HashMap<String,Object>();
				for (int i = 1; i <= metaData.getColumnCount(); i++) {
					String name				= metaData.getColumnLabel(i);	//别名，getColumnName-列名
					row.put(name, resultSet.getObject(name));
				}
				rows.add(row);
			}
			return rows;
		}
	}
	
	/** 关闭. */
	private void close (Statement statement, Connection con, ResultSet resultSet) {
		try {
			if (resultSet != null) {
				resultSet.close();
			}
			if (statement != null) {
				statement.close();
			}
			if (con != null) {
				con.close();
			}
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	
	
	//**************************静态方法****************************
	
	/** 取得总页数 */
	public static long getTotalPages(long countInDb, int everyCount) {
		return (countInDb - 1) / everyCount + 1;
	}
	
	/** 根据页数取得分页查询起始行数 */
	public static int getFirstResult(int currPage, int everyCount) {
		return (currPage - 1) * everyCount;
	}
}